package com.lagou.ipersistence.executor.resultset;

import com.lagou.ipersistence.executor.Executor;
import com.lagou.ipersistence.executor.ExecutorException;
import com.lagou.ipersistence.executor.loader.ResultLoaderMap;
import com.lagou.ipersistence.executor.parameter.ParameterHandler;
import com.lagou.ipersistence.executor.result.DefaultResultContext;
import com.lagou.ipersistence.executor.result.DefaultResultHandler;
import com.lagou.ipersistence.mapping.BoundSql;
import com.lagou.ipersistence.mapping.MappedStatement;
import com.lagou.ipersistence.mapping.ResultMap;
import com.lagou.ipersistence.mapping.ResultMapping;
import com.lagou.ipersistence.reflection.MetaClass;
import com.lagou.ipersistence.reflection.MetaObject;
import com.lagou.ipersistence.reflection.ReflectorFactory;
import com.lagou.ipersistence.reflection.factory.ObjectFactory;
import com.lagou.ipersistence.session.Configuration;
import com.lagou.ipersistence.session.ResultHandler;
import com.lagou.ipersistence.session.RowBounds;
import com.lagou.ipersistence.type.TypeHandler;
import com.lagou.ipersistence.type.TypeHandlerRegistry;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Yan Jiahong
 * Created on 2020/11/22
 */
public class DefaultResultSetHandler implements ResultSetHandler {
    private static final Object OFFERED = new Object();
    private final Executor executor;
    private final Configuration configuration;
    private final MappedStatement mappedStatement;
    private final RowBounds rowBounds;
    private final ParameterHandler parameterHandler;
    private final ResultHandler<?> resultHandler;
    private final BoundSql boundSql;
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final ObjectFactory objectFactory;
    private final ReflectorFactory reflectorFactory;

    private boolean useConstructorMappings;

    public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler,
                                   ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
        this.executor = executor;
        this.configuration = mappedStatement.getConfiguration();
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
        this.parameterHandler = parameterHandler;
        this.resultHandler = resultHandler;
        this.boundSql = boundSql;
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();
        this.reflectorFactory = configuration.getReflectorFactory();
    }

    @Override
    public <E> List<E> handleResultSet(Statement stmt) throws SQLException {
        ResultSet rs = stmt.getResultSet();
        ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration, mappedStatement.getResultMaps().get(0));
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        ResultMap resultMap = resultMaps.get(0);

        // 开始处理
        if (resultHandler == null) {
            DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds);
            return (List<E>) defaultResultHandler.getList();
        }
        return null;
    }

    private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
                                 RowBounds rowBounds) throws SQLException {
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds);
    }

    private void handleRowValuesForSimpleResultMap(
            ResultSetWrapper rsw, ResultMap resultMap,
            ResultHandler<?> resultHandler, RowBounds rowBounds) throws SQLException {
        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        ResultSet rs = rsw.getResultSet();
        while (shouldProcessMoreRows(resultContext, rowBounds) && !rs.isClosed() && rs.next()) {
            Object rowValue = getRowValue(rsw, resultMap);
            callResultHandler(resultHandler, resultContext, rowValue);
        }
    }

    private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> context, Object rowValue) {
        context.nextResultObject(rowValue);
        ((ResultHandler<Object>) resultHandler).handleResult(context);
    }

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader);
        MetaObject metaObject = configuration.newMetaObject(rowValue);
        applyAutomaticMappings(rsw, resultMap, metaObject);
        return rowValue;
    }

    private void applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject) throws SQLException {
        List<UnMappedColumnAutoMapping> autoMappings = createAutoMappings(rsw, resultMap, metaObject);
        for (UnMappedColumnAutoMapping mapping : autoMappings) {
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            metaObject.setValue(mapping.property, value);
        }
    }

    private List<UnMappedColumnAutoMapping> createAutoMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject) {
        List<UnMappedColumnAutoMapping> autoMappings = new ArrayList<>();
        final List<String> unMappedColumnNames = rsw.getUnMappedColumns();
        for (String columnName : unMappedColumnNames) {
            final String property = columnName;
            if (metaObject.hasSetter(property)) {
                if (resultMap.getMappedColumns().contains(property)) {
                    continue;
                }
                final Class<?> propertyType = metaObject.getSetterType(property);
                if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdcType(columnName))) {
                    final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                    autoMappings.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                }
            }
        }
        return autoMappings;
    }

    private static class UnMappedColumnAutoMapping {
        private final String column;
        private final String property;
        private final TypeHandler<?> typeHandler;
        private final boolean primitive;

        public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
            this.column = column;
            this.property = property;
            this.typeHandler = typeHandler;
            this.primitive = primitive;
        }
    }

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader) throws SQLException {
        this.useConstructorMappings = false;
        final List<Class<?>> constructorTypes = new ArrayList<>();
        final List<Object> constructorArgs = new ArrayList<>();
        Object resultObject = createResultObject(rsw, resultMap, constructorTypes, constructorArgs);
//        final List<ResultMapping> propertyMappings =resultMap.getPropertyResultMappings();
        return resultObject;
    }

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
                                      List<Object> constructorArgs) throws SQLException {
        final Class<?> resultType = resultMap.getType();
        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
        final List<ResultMapping> constructorResultMappings = resultMap.getConstructorResultMappings();
        if (hasTypeHandlerForResultObject(rsw, resultType)) {
            return null;
        } else if (!constructorArgs.isEmpty()) {
            return null;
        } else {
            return objectFactory.create(resultType);
        }
    }

    private boolean shouldProcessMoreRows(DefaultResultContext<Object> context, RowBounds rowBounds) {
        return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
    }

    private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
        if (rsw != null && resultMapCount < 1) {
            throw new ExecutorException("一个查询已经运行, 但是无法为MappedStatement '" + mappedStatement.getId()
                    + "'找到ResultMap");
        }
    }

    protected boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
        if (rsw.getColumnNames().size() == 1) {
            return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdcType(rsw.getColumnNames().get(0)));
        }
        return typeHandlerRegistry.hasTypeHandler(resultType);
    }
}
